// AppChooserXMLConfigFile.cpp
// 
// Copyright (c) Jason Sandys, 2009
//
// License: This code is released according to the 
// Microsoft Public License (Ms-PL) as documented at 
// http://osdappchooser.codeplex.com/license
//
//////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "AppChooserXMLConfigFile.h"
#include "OSDAppChooserDlg.h"

//************************************************************
/**
<summary>The default constructor. Doesn't do anything
specific for this type of configuration file</summary>
*/
//************************************************************
CjAppChooserXMLConfigFile::CjAppChooserXMLConfigFile(void)
{
}

CjAppChooserXMLConfigFile::~CjAppChooserXMLConfigFile(void)
{
}

//************************************************************
/**
<summary>Load the prompt for the app set from the DOM 
object which is displayed in the dialog box as static text
above the appsets combo box</summary>

<returns>Returns the prompt specified in the configuration 
file</returns>
*/
//************************************************************
CString CjAppChooserXMLConfigFile::GetAppSetPrompt(void)
{
	LoadAppSetsNode();
	CString strPrompt = _T ("");

	//Retrieve the prompt from the config file
	_variant_t v = m_eAppSets->getAttribute (APPSETS_PROMPTATTRIBUTE);

	//Make sure the prompt attribute exists in the file
	//if not, load the default prompt
	if (v.vt == VT_BSTR)
		strPrompt = VARIANT (v);
	else
		strPrompt.LoadString (IDS_DEFAULTAPPSETSPROMPT);

	return strPrompt;

}

CString CjAppChooserXMLConfigFile::GetRootAttribute(const CString& attr, UINT defaultString)
{
	CString attrValue = _T ("");
	_variant_t v;

	//Make sure the document is properly loaded and a document element exists
	if (m_docConfig->documentElement == 0)
		throw Error (false, (DWORD)IDS_ERRORROOTNODENOTLOADED);

	if (!attr.IsEmpty ())
	{
		//Retrieve the prompt from the config file
		v = m_docConfig->documentElement->getAttribute (attr.AllocSysString ());

		//Make sure the prompt attribute exists in the file
		//if not, load the default prompt
		if (v.vt == VT_BSTR)
			attrValue = VARIANT (v);
	}

	if (attr.IsEmpty () || attrValue.IsEmpty () || v.vt != VT_BSTR)
		if (defaultString)
			attrValue.LoadString (defaultString);
		else
			attrValue = _T("");

	return attrValue;
}


//************************************************************
/**
<summary>Load all of the available app sets</summary>

<param name='lstAppSets'>(out)A list of the AppSets defined in
the config file</param>
<param name='lpszDefAppSet'>(in)A default AppSet, NULL if
none are specified</param>
*/
//************************************************************
void CjAppChooserXMLConfigFile::GetAppSets (CStringList& lstAppSets, LPCTSTR lpszDefAppSet)
{
	//Load the app sets root node
	LoadAppSetsNode();

	XmlElement eAppSet;	//Temp element used to loop through AppSets
	_variant_t v;		//Temp variable to retrieve AppSet names
	CString strQuery;	//XPath query to find the AppSets

	//Set up the XPath query using the default AppSet if one is specified
	if (lpszDefAppSet == 0)
		strQuery = APPSET_ELEMENTNAME;
	else
		strQuery.Format (_T ("%s[@%s='%s']"), APPSET_ELEMENTNAME, APP_NAMEATTRIBUTE, lpszDefAppSet);
		 
	//Get all of the app set elements from the config file
	XmlNodeList appSets = m_eAppSets->selectNodes (strQuery.AllocSysString());

	if (appSets == NULL || appSets->Getlength () == 0)
	{
		if (lpszDefAppSet == 0)
			throw Error (false, (DWORD)IDS_ERRORNOAPPSETSDEFINED);
		else
			throw Error (false, (DWORD)IDS_ERRORDEFAULTAPPSETNOTFOUND);
	}

	//Loop through of the app set elements and add them to the string list passed in
	for (long nCount = 0; nCount < appSets->Getlength(); nCount++)
	{
		eAppSet = appSets->Getitem (nCount);
		v = eAppSet->getAttribute (APPSET_NAMEATTRIBUTE);

		if (v.vt == VT_BSTR)
			lstAppSets.AddTail (v.bstrVal);
	}
}

//************************************************************
/**
<summary>Get all of the apps for the selected app set.
Call back to the dialog to add the apps and groups to the 
appropriate tree ctrl</summary>

<param name='dlg'>(in)A pointer to the main dialog to call the
appropriate function to add items to the tree</param>
<param name='lpszAppSet'>(in)The AppSet that we are getting
the apps for fro the config file</param>
*/
//************************************************************
void CjAppChooserXMLConfigFile::GetApps (COSDAppChooserDlg* dlg, LPCTSTR lpszAppSet)
{
	//Load the app sets root node (if it's not already)
	LoadAppSetsNode ();
	
	//Load the apps root node (if it's not already)
	LoadAppsNode ();

	CString strQuery = _T("");
	
	//Prepare the XPath query to select the app set node
	strQuery.Format (_T ("%s[@%s='%s']"), APPSET_ELEMENTNAME, APPSET_NAMEATTRIBUTE, lpszAppSet);
	
	//Get the appset element using the query prepared above
	XmlElement eAppSet = m_eAppSets->selectSingleNode (strQuery.AllocSysString());

	if (eAppSet)
	{
		variant_t v = eAppSet->getAttribute (APPSET_SELECTALLATTRIBUTE);
		bool bSelectAll = false;

		if (v.vt == VT_BSTR)
		{
			bSelectAll = _ttoi (v.bstrVal) != 0;
		}

		ParseAppSet (eAppSet, dlg, 0, bSelectAll);
	}
}

//************************************************************
/**
<summary>Parse through the elements of an AppSet or 
sub-element/group of an AppSet and add them to the tree</summary>

<param name='eParent'>(in)The parent element to start parsing 
at</param>
<param name='dlg'>(in)A pointer to the main dialog to call the
appropriate function to add items to the tree</param>
<param name='hParent'>(in)The parent item in the tree to add
items to</param>
<param name='bParentDefault'>Indicates whether the parent item
is set to default in which case all subitems will also be 
default and thus checked initially</param>
<param name='bParentMandatory'>Indicates whether the parent item
is set to mandatory in which case all subitems will also be 
mandatory and thus checked, greyed, and disabled</param>
*/
//************************************************************
void CjAppChooserXMLConfigFile::ParseAppSet (const XmlElement& eParent, COSDAppChooserDlg* dlg, HTREEITEM hParent, bool bParentDefault, bool bParentMandatory)
{
	XmlElement eChild;
	CString strNodeName;
	CString strAppName = _T("");
	bool bMandatory = false, bDefault = false;
	long nAppIndex = -1;
	_variant_t v, v2, v3, v4;

	//Loop through all of the children of the app set element
	for (long nodeCount = 0; nodeCount < eParent->childNodes->Getlength (); nodeCount++)
	{
		//Get the child node
		eChild = eParent->childNodes->Getitem (nodeCount);

		//If the child is an element (IXMLDOMNodeType)
		if (eChild && eChild->nodeType == 1)
		{
			if (_tcscmp (eChild->nodeName, APPREF_ELEMENTNAME) == 0)
			{

				//Get the unique id of the child element
				v = eChild->getAttribute (APPREF_IDATTRIBUTE);

				//check to make sure a string was returned
				if (v.vt == VT_BSTR)
				{
					COLE2CT pcszID (v.bstrVal);

					//Find the application referenced as a child of the app set in the applications section
					if (FindApp(CString(pcszID), strAppName))
					{
						v2 = eChild->getAttribute (APPREF_MANDATORYATTRIBUTE);
						v3 = eChild->getAttribute (APPREF_DEFAULTATTRIBUTE);
					
						if (v2.vt == VT_BSTR)
							bMandatory = _ttoi (v2.bstrVal) != 0;
						else
							bMandatory = false;

						if (v3.vt == VT_BSTR)
							bDefault = _ttoi (v3.bstrVal) != 0;
						else
							bDefault = false;

						bMandatory |= bParentMandatory;
						bDefault |= bParentDefault;

						//Add the app to the tree ctrl
						dlg->AddItem (strAppName, hParent, bDefault, bMandatory, pcszID);
					}
				}
			}
			else if (_tcscmp (eChild->nodeName, APPGROUP_ELEMENTNAME) == 0)
			{
				HTREEITEM hGroup = 0;
				
				//Get the name of the child group
				v = eChild->getAttribute (APPGROUP_NAMEATTRIBUTE);

				//check to make sure a string was returned
				if (v.vt == VT_BSTR)
				{
					COLE2CT pcszGrpName (v.bstrVal);

					v2 = eChild->getAttribute (APPGROUP_MANDATORYATTRIBUTE);
					v3 = eChild->getAttribute (APPGROUP_DEFAULTATTRIBUTE);
				
					if (v2.vt == VT_BSTR)
						bMandatory = _ttoi (v2.bstrVal) != 0;
					else
						bMandatory = false;

					if (v3.vt == VT_BSTR)
						bDefault = _ttoi (v3.bstrVal) != 0;
					else
						bDefault = false;

					bMandatory |= bParentMandatory;
					bDefault |= bParentDefault;

					hGroup = dlg->AddItem (CString (pcszGrpName), hParent, bDefault, bMandatory);

					if (hGroup)
						ParseAppSet (eChild, dlg, hGroup, bDefault, bMandatory);

				}

			}
		}
		//reset values for loop iteration
		nAppIndex = -1;
		bMandatory = false;
	}

}

//************************************************************
/**
<summary>Find an app by unique GUID in the Apps section of the 
configuration file</summary>

<param name='strAppGUID'>(in)The unique ID of the App to find
under the Apps section of the config file</param>
<param name='strAppName'>(out)The name of the application
corresponding to the unique ID</param>
<returns>The index of the App in the list; used to determine
installation order and to quickly retrieve the App from the
config file</returns>
*/
//************************************************************
bool CjAppChooserXMLConfigFile::FindApp (PCTSTR pAppGUID, CString& strAppName)
{
	//Load the apps root node (if it's not already)
	LoadAppsNode ();

    TRACE1 ("Finding app with ID: %s\n", pAppGUID);

	CString strQuery = _T("");
	
	//Prepare the XPath query to select the app node by App ID
	strQuery.Format (_T ("%s[@%s='%s']"), APP_ELEMENTNAME, APP_IDATTRIBUTE, pAppGUID);
	
	//Get the app element using the query prepared above
	XmlElement eApp = m_eApps->selectSingleNode (strQuery.AllocSysString());

	if (eApp)
	{
		variant_t v = eApp->getAttribute (APP_NAMEATTRIBUTE);

		if (v.vt == VT_BSTR)
		{
			strAppName = v.bstrVal;
			return true;
		}
	}

	return false;
}

int CjAppChooserXMLConfigFile::GetAppIndex (PCTSTR pAppGUID)
{
	//Load the apps root node (if it's not already)
	LoadAppsNode ();

	//Get a list of all app elements in the app section
	//Find these by name to ensure only apps are chosen
	XmlNodeList apps = m_eApps->getElementsByTagName (APP_ELEMENTNAME);
	XmlElement app;

	for (long nodeCount = 0; nodeCount < apps->length; nodeCount++)
	{
		//Get the next node/e
		app = apps->nextNode ();

		//Make sure the element is valid, 0 is return if no more node exist in the list
		if (app)
		{
			//Get the GUID of the current app e and check if it matches the
			//one that we are looking for
			_variant_t v = app->getAttribute (APP_IDATTRIBUTE);
			
			if (v.vt == VT_BSTR && CString(v.bstrVal) == pAppGUID)
			{
				//Return the index of the app in the list
				return nodeCount;
			}
		}
	}

	return -1;
}

//************************************************************
/**
<summary>Find an app by unique GUID in the Apps section of the 
configuration file</summary>

<param name='nIndex'>(in)The index of the App to find
under the Apps section of the config file</param>
<param name='strPkgID'>(out)The package ID of the application
corresponding to the index</param>
<param name='strProgID'>(out)The program ID of the application
corresponding to the index</param>
<returns>True if the application is found, false if not</returns>
*/
//************************************************************
bool CjAppChooserXMLConfigFile::GetAppInfo (long nIndex, CString& strPkgID, CString& strProgID)
{
	//Load the apps root node (if it's not already)
	LoadAppsNode ();

	CString strQuery;
	//Format the XPath query
	strQuery.Format (_T("%s[%d]"), APP_ELEMENTNAME, nIndex + 1);

	//Get the App element using the XPath query
	XmlElement eApp = m_eApps->selectSingleNode (strQuery.AllocSysString());

	//If found get the package and program IDs
	if (eApp)
	{
		_variant_t v1 = eApp->getAttribute (APP_PKGIDATTRIBUTE);

		if (v1.vt == VT_BSTR)
		{
			_variant_t v2 = eApp->getAttribute (APP_PROGIDATTRIBUTE);

			if (v2.vt == VT_BSTR)
			{
				strPkgID = v1.bstrVal;
				strProgID = v2.bstrVal;

				TRACE3 ("%d = %s:%s\n", nIndex, strPkgID, strProgID);

				return true;
			}
		}
	}

	return false;
}

bool CjAppChooserXMLConfigFile::GetAppTooltipInfo (PCTSTR pAppGUID, CString& text, CString& icon)
{
	//Load the apps root node (if it's not already)
	LoadAppsNode ();

    TRACE1 ("Finding app with ID: %s\n", pAppGUID);

	CString strQuery = _T("");
	
	//Prepare the XPath query to select the app node by App ID
	strQuery.Format (_T ("%s[@%s='%s']"), APP_ELEMENTNAME, APP_IDATTRIBUTE, pAppGUID);
	
	//Get the app element using the query prepared above
	XmlElement eApp = m_eApps->selectSingleNode (strQuery.AllocSysString());

	if (eApp)
	{
		variant_t v = eApp->getAttribute (APP_INFOATTRIBUTE);
		variant_t v2 = eApp->getAttribute (APP_ICONATTRIBUTE);

		if (v.vt == VT_BSTR)
			text = v.bstrVal;
		else
			text = _T("");

		if (v2.vt == VT_BSTR)
			icon = v2.bstrVal;
		else
			icon = _T("");

		return true;
	}

	return false;
}

bool CjAppChooserXMLConfigFile::FindAppRequirments (PCTSTR pAppGUID, CStringList& depApps)
{
	//Load the apps root node (if it's not already)
	LoadAppsNode ();

    TRACE1 ("Finding requirement apps for app ID %s\n", pAppGUID);

	CString strQuery = _T("");
	CString appIDs;
	
	//Prepare the XPath query to select the app node by App ID
	strQuery.Format (_T ("%s[@%s='%s']"), APP_ELEMENTNAME, APP_IDATTRIBUTE, pAppGUID);
	
	//Get the app element using the query prepared above
	XmlElement eApp = m_eApps->selectSingleNode (strQuery.AllocSysString());

	if (eApp)
	{
		variant_t v = eApp->getAttribute (APP_REQUIRESATTRIBUTE);

		if (v.vt == VT_BSTR)
		{
			appIDs = v.bstrVal;
			int start = 0;
			CString id = appIDs.Tokenize ( _T(",;"), start);

			while (id != _T(""))
			{
				if (id != pAppGUID)
				{
					depApps.AddTail(id);

					FindAppRequirments(id, depApps);
				}

				id = appIDs.Tokenize ( _T(",;"), start);
			}

			return true;
		}
	}

	return false;
}

bool CjAppChooserXMLConfigFile::FindAppDependants (PCTSTR pAppGUID, CStringList& depApps)
{
	//Load the apps root node (if it's not already)
	LoadAppsNode ();

    TRACE1 ("Finding dependants apps for app ID %s\n", pAppGUID);

	CString strQuery = _T("");
	CString appID;
	
	//Prepare the XPath query to select the app node by App ID
	strQuery.Format (_T ("%s[contains(@%s,'%s')]"), APP_ELEMENTNAME, APP_REQUIRESATTRIBUTE, pAppGUID);
	
	//Get the apps elements using the query prepared above
	XmlNodeList apps = m_eApps->selectNodes (strQuery.AllocSysString());
	XmlElement app;
	variant_t v;

	if (apps != NULL && apps->Getlength () != 0)
	{
		//Loop through the apps and add them to the string list passed in
		for (long nCount = 0; nCount < apps->Getlength(); nCount++)
		{
			app = apps->Getitem (nCount);
			v = app->getAttribute (APP_IDATTRIBUTE);

			if (v.vt == VT_BSTR)
			{
				appID = v.bstrVal;
				
				if (appID != pAppGUID)
				{
					depApps.AddTail (appID);
					FindAppDependants (appID, depApps);
				}
			}
		}
	}

	return false;
}